﻿using System;
using System.Linq;
using System.Linq.Expressions;
using System.Collections.Generic;
using System.Reflection;
using nRoute.Utilities;
using nRoute.Components.Collections;
#if (!SILVERLIGHT)
using System.Collections.Concurrent;
#endif

namespace nRoute.Components.Composition
{
    public class TypeBuilder
        : ITypeBuilder, IDisposable
    {
#if (!WINRT && !WP7)
        private static readonly MethodInfo GETINSTANCE_METHODINFO = typeof(IResourceResolver).GetMethod("GetInstance");
#endif

        private const string BUILD_MUSTNOTBE_STATIC = "Build is only supported on instance/non-classes, '{0}' is not supported";
        private const string BUILD_MUSTBE_PUBLIC = "Build is only supported on public classes, '{0}' is not supported";
        private const string BUILD_MUSTNOTBE_ABSTRACT = "Build is not supported on abstract classes, '{0}' is not supported";
        private const string BUILD_NONREFECTED_NOTSUPPORTED = "Only reflected building of Objects is supported with Windows Phone Runtime";
        private const int INITIAL_CAPACITY = 1001;
        private const int CONCCURRENCY_LEVEL_FACTOR = 2;

        private readonly Object _lock = new Object();

#if (!WINRT)
        private readonly ConcurrentDictionary<Type, TypeDescriptor> _buildPlans;
        private readonly ConcurrentDictionary<Type, Func<ITypeBuilder, Object>> _buildersCache;
        private readonly ConcurrentDictionary<Type, Func<ITypeBuilder, Object>> _buildersReflectedCache;
#else
        private readonly ConcurrentDictionary<TypeInfo, TypeDescriptor> _buildPlans;
        private readonly ConcurrentDictionary<TypeInfo, Func<ITypeBuilder, Object>> _buildersCache;
        private readonly ConcurrentDictionary<TypeInfo, Func<ITypeBuilder, Object>> _buildersReflectedCache;
#endif
        private readonly IResourceCatalog _resourceCatalog;

        public TypeBuilder(IResourceCatalog resourceCatalog)
        {
            Guard.ArgumentNotNull(resourceCatalog, "resourceCatalog");
            _resourceCatalog = resourceCatalog;

#if (!WINRT)
            _buildPlans = new ConcurrentDictionary<Type, TypeDescriptor>(Compatability.PROC_COUNT * CONCCURRENCY_LEVEL_FACTOR, INITIAL_CAPACITY);
            _buildersCache = new ConcurrentDictionary<Type, Func<ITypeBuilder, Object>>(Compatability.PROC_COUNT * CONCCURRENCY_LEVEL_FACTOR, INITIAL_CAPACITY);
            _buildersReflectedCache = new ConcurrentDictionary<Type, Func<ITypeBuilder, Object>>(Compatability.PROC_COUNT * CONCCURRENCY_LEVEL_FACTOR, INITIAL_CAPACITY);
#else
            _buildPlans = new ConcurrentDictionary<TypeInfo, TypeDescriptor>(Compatability.PROC_COUNT * CONCCURRENCY_LEVEL_FACTOR, INITIAL_CAPACITY);
            _buildersCache = new ConcurrentDictionary<TypeInfo, Func<ITypeBuilder, Object>>(Compatability.PROC_COUNT * CONCCURRENCY_LEVEL_FACTOR, INITIAL_CAPACITY);
            _buildersReflectedCache = new ConcurrentDictionary<TypeInfo, Func<ITypeBuilder, Object>>(Compatability.PROC_COUNT * CONCCURRENCY_LEVEL_FACTOR, INITIAL_CAPACITY);
#endif
        }

#region ITypeBuilder related

        public IResourceCatalog ResourceCatalog
        {
            get { return _resourceCatalog; }
        }

        public T BuildType<T>()
        {
#if (!WINRT)
            return (T)Build(typeof(T), false);
#else
            return (T)Build(typeof(T).GetTypeInfo(), true);
#endif
        }

        public T BuildType<T>(bool useReflection)
        {
#if (!WINRT)
            return (T)Build(typeof(T), useReflection);
#else
            return (T)Build(typeof(T).GetTypeInfo(), useReflection);
#endif
        }

        public Object BuildType(Type type)
        {
#if (!WINRT)
            return Build(type, false);
#else
            return Build(type.GetTypeInfo(), false);
#endif
        }

        public Object BuildType(Type type, bool useReflection)
        {
#if (!WINRT)
            return Build(type, useReflection);
#else
            return Build(type.GetTypeInfo(), useReflection);
#endif
        }

#endregion

#region Helpers

#if (!WINRT)
        private Object Build(Type type, bool useReflection)
#else
        private Object Build(TypeInfo type, bool useReflection)
#endif
        {
            Guard.ArgumentNotNull(type, "type");
            Guard.ArgumentValue(type.IsSealed && type.IsAbstract, "type", BUILD_MUSTNOTBE_STATIC, type.FullName);
#if (SILVERLIGHT)
            Guard.ArgumentValue(type.IsNotPublic, "type", BUILD_MUSTBE_PUBLIC, type.FullName);
#endif
            Guard.ArgumentValue(type.IsAbstract, "type", BUILD_MUSTNOTBE_ABSTRACT, type.FullName);

            // we try and get the activator else we need to 
            Func<ITypeBuilder, Object> _builder = null;
#if (!WINRT && !WP7)
            if (!useReflection)
            {
                if (!_buildersCache.TryGetValue(type, out _builder))
                {
                    _builder = CreateCompiledDescriptorBuilder(type);
                    if (!_buildersCache.TryAdd(type, _builder))
                    {
                        // key already exists - loop back?
                        return BuildType(type, useReflection);
                    }
                }
            }
            else
            {
                if (!_buildersReflectedCache.TryGetValue(type, out _builder))
                {
                    _builder = CreateReflectedDescriptorBuilder(type);
                    if (!_buildersReflectedCache.TryAdd(type, _builder))
                    {
                        // key already exists - loop back?
                        return BuildType(type, useReflection);
                    }
                }
            }
                
#else
            // note non-reflection mode not supported
            if (!_buildersReflectedCache.TryGetValue(type, out _builder))
            {
                _builder = CreateReflectedDescriptorBuilder(type);
                if (!_buildersReflectedCache.TryAdd(type, _builder))
                {
                    // key already exists - loop back?
                    return BuildType(type.DeclaringType, useReflection);
                }
            }
#endif

            // and return
            return _builder(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                _buildersCache.Clear();
                _buildersReflectedCache.Clear();
                _buildPlans.Clear();
            }
        }

#endregion

#region Descriptor Builders

#if (!WINRT && !WP7)

        private Func<ITypeBuilder, Object> CreateCompiledDescriptorBuilder(Type type)
        {
            Guard.ArgumentNotNull(type, "type");

            // create a new profile
            var _typeDescriptor = _buildPlans.GetOrAdd(type, (t) => new TypeDescriptor(t));
            _typeDescriptor.CreateNewProfile(this.ResourceCatalog);      // this resets the data

            var _blockParameters = new List<ParameterExpression>();
            var _blockExpressions = new List<Expression>();
            var _resolvers = new List<IResourceResolver>();

            // input expresions
            var _resolversExpression = Expression.Parameter(typeof(IResourceResolver[]), "resolvers");
            var _typeBuilderExpression = Expression.Parameter(typeof(ITypeBuilder), "typeBuilder");
            
            // output expression 
            var _resourceObjectExpression = Expression.Parameter(type, "result");
            _blockParameters.Add(_resourceObjectExpression);
            _blockExpressions.Add(Expression.Assign(_resourceObjectExpression, Expression.Default(type)));

            // 1. Constructor Parameters
            // note, this also works with non-public constructors
            Expression _newExpression = default(NewExpression);
            if (_typeDescriptor.DefaultConstructorDescriptor.ParameterDescriptors.Count() != 0)
            {
                var _parameters = new List<Expression>();

                foreach (var _parameterDescriptor in _typeDescriptor.DefaultConstructorDescriptor.ParameterDescriptors)
                {
                    var _parameterResolver = _parameterDescriptor.Resolver;
                    var _parameterType = _parameterDescriptor.Parameter.ParameterType;

                    // create a local variable
                    var _localVariableExpression = Expression.Variable(_parameterType, String.Format("local{0}", _resolvers.Count));
                    
                    // create an assign, like:
                    // var _resolve0 = (ISomeObjectType)resolvers[0].GetInstance(typeof(SomeResolveResolver), typeBuilder);
                    var _typeParameterExpression = Expression.Constant(_parameterType, typeof(Type));
                    var _assignExpression = Expression.Assign(
                                                _localVariableExpression,
                                                Expression.Convert(Expression.Call(Expression.ArrayAccess(_resolversExpression, Expression.Constant(_resolvers.Count, typeof(int))),
                                                GETINSTANCE_METHODINFO, _typeParameterExpression, _typeBuilderExpression), _parameterType));

                    // we also keep track of the parameters and resolvers
                    _blockExpressions.Add(_assignExpression);
                    _blockParameters.Add(_localVariableExpression);
                    _resolvers.Add(_parameterResolver);
                    _parameters.Add(_localVariableExpression);
                }

                // then new up with variable, like:
                // result = new SomeResource(local0, local1);
                _newExpression = Expression.New(_typeDescriptor.DefaultConstructorDescriptor.Constructor, _parameters.ToArray());
            }
            else
            {
                // simple call to default const
                _newExpression = Expression.New(_typeDescriptor.DefaultConstructorDescriptor.Constructor);
            }

            // create new, expression and assign to variable, like:
            // result = new SomeObject(); or result = new SomeObject(param1, param2);
            _blockExpressions.Add(Expression.Assign(_resourceObjectExpression, _newExpression));

            // 3. Fields
            if (_typeDescriptor.FieldsDescriptor != null)
            {
                foreach (var _fieldDescriptor in _typeDescriptor.FieldsDescriptor)
                {
                    var _fieldInfo = _fieldDescriptor.Field;
                    var _fieldResolver = _fieldDescriptor.Resolver;
                    var _fieldType = _fieldDescriptor.Field.FieldType;

                    // create an assign, like:
                    // result._internalField = (ISomeObjectType)resolvers[0].GetInstance(typeof(SomeResolveResolver), typeBuilder);
                    var _typeParameterExpression = Expression.Constant(_fieldType, typeof(Type));
                    var _assignExpression = Expression.Assign(
                                                Expression.Field(_resourceObjectExpression, _fieldInfo), 
                                                Expression.Convert(Expression.Call(Expression.ArrayAccess(_resolversExpression, Expression.Constant(_resolvers.Count, typeof(int))),
                                                    GETINSTANCE_METHODINFO, _typeParameterExpression, _typeBuilderExpression), _fieldType));

                    // we keep track of the resolver
                    _blockExpressions.Add(_assignExpression);
                    _resolvers.Add(_fieldResolver);
                }
            }

            // 4. Properties - note properties don't use emit so they can work as is
            if (_typeDescriptor.PropertiesDescriptor != null)
            {
                foreach (var _propertyDescriptor in _typeDescriptor.PropertiesDescriptor)
                {
                    var _propertySetterMethod = _propertyDescriptor.Property.GetSetMethod();
                    var _propertyResolver = _propertyDescriptor.Resolver;
                    var _propertyType = _propertyDescriptor.Property.PropertyType;

                    // create an assign, like:
                    // result.MyProperty = (ISomeObjectType)resolvers[0].GetInstance(typeof(SomeResolveResolver), typeBuilder);
                    var _typeParameterExpression = Expression.Constant(_propertyType, typeof(Type));
                    var _assignExpression = Expression.Assign(
                                                Expression.Property(_resourceObjectExpression, _propertySetterMethod),
                                                Expression.Convert(Expression.Call(Expression.ArrayAccess(_resolversExpression, Expression.Constant(_resolvers.Count, typeof(int))),
                                                    GETINSTANCE_METHODINFO, _typeParameterExpression, _typeBuilderExpression), _propertyType));

                    // we keep track of the resolver
                    _blockExpressions.Add(_assignExpression);
                    _resolvers.Add(_propertyResolver);
                }
            }

            // and finally create the return labels
            var _returnTarget = Expression.Label(typeof(Object));
            var _returnExpression = Expression.Return(_returnTarget, _resourceObjectExpression, typeof(Object));
            var _returnLabel = Expression.Label(_returnTarget, _returnExpression);

            // add 
            _blockExpressions.Add(_returnExpression);
            _blockExpressions.Add(_returnLabel);

            // create block
            var _blockExpression = Expression.Block(_blockParameters.ToArray(), _blockExpressions.ToArray());
            var _funcExpression = Expression.Lambda<Func<IResourceResolver[], ITypeBuilder, Object>>(_blockExpression, _resolversExpression, _typeBuilderExpression);
            var _func = _funcExpression.Compile();

            // we now wrap it
            var _resolversArray = _resolvers.ToArray();
            Expression<Func<ITypeBuilder, Object>> _wrappedFunc = (b) => _func(_resolversArray, b);

            // and return
            return _wrappedFunc.Compile();
        }

        private Func<ITypeBuilder, Object> CreateDescriptorBuilder(Type type)
        {
            Guard.ArgumentNotNull(type, "type");

            // create a new profile
            var _typeDescriptor = _buildPlans.GetOrAdd(type, (t) => new TypeDescriptor(t));            
            _typeDescriptor.CreateNewProfile(this.ResourceCatalog);      // this resets the data

            // holds the actions
            var _typeBuildActions = new List<Action<Object, ITypeBuilder>>();

            // 1. Constructor Parameters
            var _typeConstructorParameters = new List<Func<ITypeBuilder, Object>>();
            foreach (var _parameterDescriptor in _typeDescriptor.DefaultConstructorDescriptor.ParameterDescriptors)
            {
                var _parameterResolve = _parameterDescriptor.Resolver;
                var _parameterType = _parameterDescriptor.Parameter.ParameterType;
                _typeConstructorParameters.Add((b) => _parameterResolve.GetInstance(_parameterType, b));
            }

            // 2. Constructor
            var _typeConstructor = (Func<Func<ITypeBuilder, Object>[], ITypeBuilder, Object>)null;
            if (_typeDescriptor.DescriptorType.IsPublic && _typeDescriptor.DefaultConstructorDescriptor.Constructor.IsPublic)
            {
                // using dynamic method
                var _constructorDelegate = _typeDescriptor.DefaultConstructorDescriptor.ConstructorDelegate;
                var _constructorParameterDelegate = _typeDescriptor.DefaultConstructorDescriptor.ConstructorParameterDelegate;

                if (_typeConstructorParameters.Count > 0)
                {
                    _typeConstructor = new Func<Func<ITypeBuilder, Object>[], ITypeBuilder, Object>((p, b) => _constructorParameterDelegate.Invoke(p.Select((f) => f(b)).ToArray()));
                }
                else
                {
                    _typeConstructor = new Func<Func<ITypeBuilder, Object>[], ITypeBuilder, Object>((p, b) => _constructorDelegate());
                }
            }
            else
            {
                // using reflection
                var _constructorInfo = _typeDescriptor.DefaultConstructorDescriptor.Constructor;
                if (_typeConstructorParameters.Count > 0)
                {
                    _typeConstructor = new Func<Func<ITypeBuilder, Object>[], ITypeBuilder, Object>((p, b) => _constructorInfo.Invoke(p.Select((f) => f(b)).ToArray()));
                }
                else
                {
                    _typeConstructor = new Func<Func<ITypeBuilder, Object>[], ITypeBuilder, Object>((p, b) => _constructorInfo.Invoke(new object[] { }));
                }
            }

            // 3. Fields
            if (_typeDescriptor.FieldsDescriptor != null)
            {
                foreach (var _fieldDescriptor in _typeDescriptor.FieldsDescriptor)
                {
                    var _fieldInfo = _fieldDescriptor.Field;
                    var _fieldSetter = _fieldDescriptor.FieldSetterDelegate;
                    var _fieldResolver = _fieldDescriptor.Resolver;
                    var _fieldType = _fieldDescriptor.Field.FieldType;
                    if (_fieldInfo.IsPublic)
                    {
                        _typeBuildActions.Add((o, b) => _fieldSetter(o, _fieldResolver.GetInstance(_fieldType, b)));
                    }
                    else
                    {
                        _typeBuildActions.Add((o, b) => _fieldInfo.SetValue(o, _fieldResolver.GetInstance(_fieldType, b)));
                    }
                }
            }

            // 4. Properties - note properties don't use emit so they can work as is
            if (_typeDescriptor.PropertiesDescriptor != null)
            {
                foreach (var _propertyDescriptor in _typeDescriptor.PropertiesDescriptor)
                {
                    var _propertySetter = _propertyDescriptor.PropertySetterDelegate;
                    var _propertyResolver = _propertyDescriptor.Resolver;
                    var _propertyType = _propertyDescriptor.Property.PropertyType;                    
                    _typeBuildActions.Add((o, b) => _propertySetter(o, _propertyResolver.GetInstance(_propertyType, b)));
                }
            }

            // convert to array
            var _typeConstructorParametersArray = _typeConstructorParameters.ToArray();
            var _typeBuildActionsArray = _typeBuildActions.ToArray();

            // and return
            return (b) =>
            {
                // build object with constructor parameters
                var _object = _typeConstructor(_typeConstructorParametersArray, b);

                // build fields and properties
                foreach (var _action in _typeBuildActionsArray)
                {
                    _action(_object, b);
                }

                // and return
                return _object;
            };
        }

#endif

#if (!WINRT)
        private Func<ITypeBuilder, Object> CreateReflectedDescriptorBuilder(Type type)
#else
        private Func<ITypeBuilder, Object> CreateReflectedDescriptorBuilder(TypeInfo type)
#endif
        {
            Guard.ArgumentNotNull(type, "type");

            // create a new profile
            var _typeDescriptor = _buildPlans.GetOrAdd(type, (t) => new TypeDescriptor(t));
            _typeDescriptor.CreateNewProfile(this.ResourceCatalog);      // this resets the data

            // holds the actions
            var _typeBuildActions = new List<Action<Object, ITypeBuilder>>();

            // 1. Constructor Parameters
            var _typeConstructorParameters = new List<Func<ITypeBuilder, Object>>();
            foreach (var _parameterDescriptor in _typeDescriptor.DefaultConstructorDescriptor.ParameterDescriptors)
            {
                var _parameterResolver = _parameterDescriptor.Resolver;
                var _parameterType = _parameterDescriptor.Parameter.ParameterType;
                _typeConstructorParameters.Add((b) => _parameterResolver.GetInstance(_parameterType, b));
            }

            // 2. Constructor
            var _constructorInfo = _typeDescriptor.DefaultConstructorDescriptor.Constructor;
            var _typeConstructor = default(Func<Func<ITypeBuilder, Object>[], ITypeBuilder, Object>);
            if (_typeConstructorParameters.Count > 0)
            {
                _typeConstructor = new Func<Func<ITypeBuilder, Object>[], ITypeBuilder, Object>((p, b) => _constructorInfo.Invoke(p.Select((f) => f(b)).ToArray()));
            }
            else
            {
                _typeConstructor = new Func<Func<ITypeBuilder, Object>[], ITypeBuilder, Object>((p, b) => _constructorInfo.Invoke(new object[] { }));
            }

            // 3. Fields
            if (_typeDescriptor.FieldsDescriptor != null)
            {
                foreach (var _fieldDescriptor in _typeDescriptor.FieldsDescriptor)
                {
                    var _fieldInfo = _fieldDescriptor.Field;
                    var _fieldResolver = _fieldDescriptor.Resolver;
                    var _fieldType = _fieldDescriptor.Field.FieldType;
                    _typeBuildActions.Add((o, b) => _fieldInfo.SetValue(o, _fieldResolver.GetInstance(_fieldType, b)));
                }
            }

            // 4. Properties
            if (_typeDescriptor.PropertiesDescriptor != null)
            {
                foreach (var _propertyDescriptor in _typeDescriptor.PropertiesDescriptor)
                {
                    var _propertyInfo = _propertyDescriptor.Property;
                    var _propertyResolver = _propertyDescriptor.Resolver;
                    var _propertyType = _propertyDescriptor.Property.PropertyType;
                    _typeBuildActions.Add((o, b) => _propertyInfo.SetValue(o, _propertyResolver.GetInstance(_propertyType, b), null));
                }
            }

            // convert to array
            var _typeConstructorParametersArray = _typeConstructorParameters.ToArray();
            var _typeBuildActionsArray = _typeBuildActions.ToArray();

            // and return
            return (b) =>
            {
                // build object with constructor parameters
                var _object = _typeConstructor(_typeConstructorParametersArray, b);

                // build fields and properties
                foreach (var _action in _typeBuildActionsArray)
                {
                    _action(_object, b);
                }

                // and return
                return _object;
            };
        }

#endregion

#region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

#endregion

        ~TypeBuilder() { Dispose(false); }

    }
}



